package cn.mujiankeji.mbrowser.工具.操作

import cn.mujiankeji.mbrowser.工具.mlog
import cn.mujiankeji.mbrowser.工具.网络.网络操作
import cn.mujiankeji.mbrowser.数据.数据库.AppDatabase
import cn.mujiankeji.mbrowser.数据.数据库.DBUserScript
import cn.mujiankeji.mbrowser.数据.数据库.DaoUserScript
import cn.mujiankeji.mbrowser.界面.小窗页.请求.用户脚本安装窗口状态
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.datetime.Clock
import org.koin.core.context.GlobalContext
import java.io.File
import java.util.UUID
import java.util.regex.Pattern

object 用户脚本操作 {

    // 脚本匹配缓存结构
    data class 脚本缓存项(
        val 脚本ID: Long,
        val 脚本路径: String,
        val 匹配规则列表: List<String>,
        val 排除规则列表: List<String>,
        val 运行时机列表: List<String>,
        val 更新时间: Long,  // 用于判断缓存是否过期
        val 名称: String     // 仅用于日志和调试
    )
    
    // 全局脚本缓存
    private var 脚本缓存: List<脚本缓存项> = emptyList()
    
    // 最后更新时间，用于判断是否需要刷新缓存
    private var 缓存更新时间: Long = 0
    
    /**
     * 获取脚本缓存
     * 如果缓存不存在或已过期，则从数据库重新加载
     */
    suspend fun 获取脚本缓存(强制刷新: Boolean = false): List<脚本缓存项> = withContext(Dispatchers.IO) {
        // 获取数据库中的最新脚本更新时间
        val dao = GlobalContext.get().get<AppDatabase>().用户脚本()
        
        // 如果强制刷新或缓存为空，则更新缓存
        if (强制刷新 || 脚本缓存.isEmpty()) {
            更新脚本缓存(dao)
            return@withContext 脚本缓存
        }
        
        // 返回现有缓存
        return@withContext 脚本缓存
    }
    
    /**
     * 更新脚本缓存
     * 从数据库加载所有启用的脚本，并构建缓存
     */
    private suspend fun 更新脚本缓存(dao: DaoUserScript) {
        val 所有脚本 = dao.获取所有启用脚本()
        val 新缓存 = mutableListOf<脚本缓存项>()
        
        for (脚本 in 所有脚本) {
            // 提取排除规则
            val 排除规则 = 脚本.匹配地址.filter { it.startsWith("exclude:") }
                .map { it.substring(8) }
            
            // 提取匹配规则
            val 匹配规则 = 脚本.匹配地址.filter { !it.startsWith("exclude:") }
            
            // 创建缓存项
            val 缓存项 = 脚本缓存项(
                脚本ID = 脚本.id,
                脚本路径 = 脚本.获取文件路径(),
                匹配规则列表 = 匹配规则,
                排除规则列表 = 排除规则,
                运行时机列表 = 脚本.运行时机,
                更新时间 = 脚本.更新时间,
                名称 = 脚本.名称
            )
            
            新缓存.add(缓存项)
        }
        
        // 更新全局缓存
        脚本缓存 = 新缓存
        缓存更新时间 = System.currentTimeMillis()
    }
    
    /**
     * 匹配URL获取适用的脚本
     * 根据URL和运行时机返回匹配的脚本路径列表
     * 
     * @param url 要匹配的URL
     * @param 运行时机 运行时机（document-start, document-end, document-idle）
     * @return 匹配的脚本路径和脚本ID的列表
     */
    suspend fun 匹配URL获取脚本(url: String, 运行时机: String): List<Pair<String, Long>> = withContext(Dispatchers.IO) {
        val 缓存 = 获取脚本缓存()
        val 结果 = mutableListOf<Pair<String, Long>>()
        
        for (缓存项 in 缓存) {
            // 检查运行时机
            if (!缓存项.运行时机列表.contains(运行时机)) continue
            
            // 检查是否匹配
            val 匹配 = 缓存项.匹配规则列表.any { 规则 -> 
                匹配URL规则(url, 规则)
            }
            
            // 检查是否被排除
            val 排除 = 缓存项.排除规则列表.any { 规则 -> 
                匹配URL规则(url, 规则)
            }
            
            // 如果匹配且未被排除，添加到结果中
            if (匹配 && !排除) {
                结果.add(Pair(缓存项.脚本路径, 缓存项.脚本ID))
            }
        }
        
        return@withContext 结果
    }
    
    /**
     * 匹配URL与规则
     * 支持油猴脚本的通配符匹配规则
     */
    private fun 匹配URL规则(url: String, 规则: String): Boolean {
        // 处理空规则
        if (规则.isEmpty()) return false
        
        try {
            // 处理正则表达式规则
            if (规则.startsWith("/") && 规则.endsWith("/")) {
                val 正则 = 规则.substring(1, 规则.length - 1).toRegex()
                return url.matches(正则)
            }
            
            // 处理精确匹配
            if (!规则.contains("*")) {
                return url == 规则
            }
            
            // 处理通配符规则
            val 规则正则 = 规则
                .replace(".", "\\.")
                .replace("?", "\\?")
                .replace("*", ".*")
                .replace("+", "\\+")
                
            return url.matches(规则正则.toRegex())
        } catch (e: Exception) {
            mlog("URL匹配异常: $规则", e)
            return false
        }
    }
    
    /**
     * 清除脚本缓存
     * 在脚本安装、更新或删除后调用
     */
    fun 清除脚本缓存() {
        脚本缓存 = emptyList()
        缓存更新时间 = 0
        mlog("已清除脚本缓存")
    }

    /**
     * 从脚本文本解析元数据，创建DBUserScript对象
     * @param 脚本文本 脚本的完整文本内容
     * @return 解析后的DBUserScript对象
     */
    fun 解析(脚本文本: String): DBUserScript {
        val script = DBUserScript()
        
        // 解析脚本元数据
        val regex = "(^|\\s)((//)|(\\*))\\s+@([a-zA-Z0-9:_-]+)\\s+(.+)"
        val matcher = Pattern
            .compile(regex, Pattern.CASE_INSENSITIVE)
            .matcher(脚本文本)

        val 匹配地址 = mutableListOf<String>()
        val 排除地址 = mutableListOf<String>()
        val 油猴API = mutableListOf<String>()
        val 依赖资源 = mutableListOf<String>()
        val 预加载资源 = mutableListOf<String>()
        val 跨域连接 = mutableListOf<String>()
        val 运行时机 = mutableListOf<String>()

        while (matcher.find()) {
            if (matcher.groupCount() != 6) continue
            val name = matcher.group(5) ?: continue
            val value = matcher.group(6) ?: continue
            
            when {
                name.equals("name", true) -> {
                    if (value.trim().isNotEmpty()) {
                        script.名称 = value.trim()
                    }
                }
                name.equals("enable", true) -> {
                    script.启用 = value.toIntOrNull() == 1
                }
                name.equals("author", true) -> {
                    script.作者 = value
                }
                name.equals("description", true) -> {
                    script.描述 = value
                }
                name.equals("version", true) -> {
                    script.版本 = value
                }
                name.equals("run-at", true) -> {
                    运行时机.add(value)
                }
                name.equals("updateURL", true) -> {
                    if (value.isNotEmpty()) {
                        script.更新地址 = value.trim()
                    }
                }
                name.equals("match", true) || name.equals("include", true) -> {
                    val v2 = value.trim()
                    if (v2.isNotEmpty() && !匹配地址.contains(v2)) {
                        匹配地址.add(v2)
                    }
                }
                name.equals("exclude", true) -> {
                    val v2 = value.trim()
                    if (v2.isNotEmpty() && !排除地址.contains(v2)) {
                        排除地址.add(v2)
                    }
                }
                name.equals("grant", true) -> {
                    if (value.trim().isNotEmpty()) {
                        油猴API.add(value.trim())
                    }
                }
                name.equals("require", true) -> {
                    if (value.isNotEmpty() && !依赖资源.contains(value)) {
                        依赖资源.add(value)
                    }
                }
                name.equals("resource", true) -> {
                    if (value.trim().isNotEmpty()) {
                        预加载资源.add(value.trim())
                    }
                }
                name.equals("connect", true) -> {
                    if (value.trim().isNotEmpty()) {
                        跨域连接.add(value.trim())
                    }
                }
                name.equals("antifeature", true) -> {
                    if (value.trim().isNotEmpty()) {
                        script.不良引导说明 = value.trim()
                    }
                }
                else -> {
                    mlog("未知的脚本元数据参数: ${name}")
                }
            }
        }

        // 设置解析到的列表数据
        script.匹配地址 = 匹配地址
        script.运行时机 = if (运行时机.isEmpty()) listOf("document-end") else 运行时机
        script.油猴API = 油猴API
        script.依赖资源 = 依赖资源
        script.预加载资源 = 预加载资源
        script.跨域连接 = 跨域连接
        
        // 如果没有标识符，生成一个
        if (script.标识符.isEmpty()) {
            script.标识符 = UUID.randomUUID().toString()
        }
        
        // 更新时间设置为当前时间
        script.更新时间 = Clock.System.now().epochSeconds

        return script
    }

    /**
     * 判断两个脚本是否实质上相同
     * 主要比较名称、作者和版本，这些通常能唯一标识一个脚本
     * 
     * @param 脚本1 第一个脚本
     * @param 脚本2 第二个脚本
     * @return 如果脚本实质上相同则返回true
     */
    fun 是否相同脚本(脚本1: DBUserScript, 脚本2: DBUserScript): Boolean {
        // 如果名称不同，则认为是不同脚本
        if (脚本1.名称 != 脚本2.名称) return false
        
        // 如果名称相同但作者不同，可能是不同人的同名脚本
        if (脚本1.作者.isNotEmpty() && 脚本2.作者.isNotEmpty() && 脚本1.作者 != 脚本2.作者) return false
        
        // 如果有相同的更新地址，则认为是同一脚本
        if (脚本1.更新地址.isNotEmpty() && 脚本1.更新地址 == 脚本2.更新地址) return true
        
        // 如果名称和作者都相同，大概率是同一脚本
        return true
    }

    /**
     * 显示脚本安装确认窗口
     * 
     * @param 脚本源代码 脚本源代码
     * @param userScriptDao 脚本数据库DAO
     * @param 完成回调 安装完成后的回调，参数为是否成功安装和脚本ID
     */
    fun 请求安装脚本(脚本源代码: String,
                     userScriptDao: DaoUserScript = GlobalContext.get().get<AppDatabase>().用户脚本(),
                     完成回调: (Boolean, Long) -> Unit = { _, _ -> }) {
        // 使用用户脚本安装窗口状态的静态方法显示安装窗口
        用户脚本安装窗口状态.显示安装窗口(脚本源代码, userScriptDao, 完成回调)
    }

    /**
     * 安装脚本
     * 
     * @param 源代码 脚本源代码
     * @param userScriptDao 脚本数据库DAO
     * @return 安装结果 Pair(是否成功, 脚本ID)
     */
    suspend fun 安装脚本(源代码: String, userScriptDao: DaoUserScript): Pair<Boolean, Long>
    = withContext(Dispatchers.IO) {
        try {
            // 解析脚本元数据
            val 新脚本 = 解析(源代码)
            
            // 检查是否已存在相同脚本
            val 所有脚本 = userScriptDao.获取所有启用脚本()
            var 已存在脚本: DBUserScript? = null
            
            // 查找可能相同的脚本
            for (脚本 in 所有脚本) {
                if (是否相同脚本(脚本, 新脚本)) {
                    已存在脚本 = 脚本
                    break
                }
            }
            
            // 如果找到相同脚本，进行版本比较
            if (已存在脚本 != null) {
                val 版本比较 = 比较版本(新脚本.版本, 已存在脚本.版本)
                
                if (版本比较 <= 0) {
                    // 新版本不比已有版本新，询问是否覆盖
                    mlog("脚本已存在，且新版本不比已有版本新")
                    return@withContext Pair(false, 已存在脚本.id)
                }
                
                // 新版本比已有版本新，更新脚本
                新脚本.id = 已存在脚本.id
                新脚本.标识符 = 已存在脚本.标识符
                新脚本.启用 = 已存在脚本.启用  // 保留原有启用状态
                
                // 保存脚本内容
                val 文件路径 = 新脚本.获取文件路径()
                File(文件路径).writeText(源代码)
                
                // 更新数据库
                userScriptDao.更新用户脚本(新脚本)
                
                // 清除缓存
                清除脚本缓存()
                
                return@withContext Pair(true, 新脚本.id)
            }
            
            // 如果是新脚本，直接安装
            // 保存脚本内容
            val 文件路径 = 新脚本.获取文件路径()
            File(文件路径).parentFile?.mkdirs()  // 确保目录存在
            File(文件路径).writeText(源代码)
            
            // 插入数据库
            val 脚本ID = userScriptDao.插入用户脚本(新脚本)
            
            // 清除缓存
            清除脚本缓存()
            
            return@withContext Pair(true, 脚本ID)
            
        } catch (e: Exception) {
            mlog("安装脚本失败: ${e.message}")
            e.printStackTrace()
            return@withContext Pair(false, -1)
        }
    }

    /**
     * 检查脚本是否有更新
     * @param 更新地址 脚本更新地址
     * @param 当前版本 脚本当前版本
     * @return 是否有新版本可用
     */
    suspend fun 检查更新(更新地址: String, 当前版本: String): Boolean {
        if (更新地址.isEmpty()) return false

        return try {
            val 远程内容 = 网络操作.获取文本(更新地址) ?: return false
            val 远程版本 = 从脚本提取版本(远程内容)
            比较版本(远程版本, 当前版本) > 0
        } catch (e: Exception) {
            mlog("检查脚本更新失败: ${e.message}")
            false
        }
    }

    /**
     * 从脚本内容中提取版本号
     */
    private fun 从脚本提取版本(脚本内容: String): String {
        val 版本正则 = Regex("// @version\\s+(.+)")
        val 匹配结果 = 版本正则.find(脚本内容)
        return 匹配结果?.groupValues?.getOrNull(1) ?: ""
    }

    /**
     * 比较两个版本号
     * @return 正数表示v1大于v2，负数表示v1小于v2，0表示相等
     */
    fun 比较版本(v1: String, v2: String): Int {
        if (v1.isEmpty() && v2.isEmpty()) return 0
        if (v1.isEmpty()) return -1
        if (v2.isEmpty()) return 1
        
        val parts1 = v1.split(".").map { it.toIntOrNull() ?: 0 }
        val parts2 = v2.split(".").map { it.toIntOrNull() ?: 0 }

        for (i in 0 until maxOf(parts1.size, parts2.size)) {
            val p1 = parts1.getOrNull(i) ?: 0
            val p2 = parts2.getOrNull(i) ?: 0
            if (p1 != p2) return p1 - p2
        }
        return 0
    }
    
    /**
     * 删除脚本
     * @param 脚本 要删除的脚本
     * @param userScriptDao 脚本数据库DAO
     * @return 是否删除成功
     */
    suspend fun 删除脚本(脚本: DBUserScript, userScriptDao: DaoUserScript): Boolean = withContext(Dispatchers.IO) {
        try {
            // 删除脚本文件
            val 文件路径 = 脚本.获取文件路径()
            val 文件 = File(文件路径)
            if (文件.exists()) {
                文件.delete()
            }
            
            // 从数据库删除
            userScriptDao.删除用户脚本(脚本)
            
            // 清除缓存
            清除脚本缓存()
            
            true
        } catch (e: Exception) {
            mlog("删除脚本失败: ${e.message}")
            false
        }
    }
    
    /**
     * 读取脚本内容
     * @param 脚本 要读取的脚本
     * @return 脚本内容，如果读取失败则返回null
     */
    fun 读取脚本内容(脚本: DBUserScript): String? {
        val 文件路径 = 脚本.获取文件路径()
        val 文件 = File(文件路径)
        return if (文件.exists()) {
            try {
                文件.readText()
            } catch (e: Exception) {
                mlog("读取脚本内容失败: ${e.message}")
                null
            }
        } else {
            null
        }
    }
    
    /**
     * 按路径读取脚本内容
     */
    fun 按路径读取脚本内容(文件路径: String): String? {
        val 文件 = File(文件路径)
        return if (文件.exists()) {
            try {
                文件.readText()
            } catch (e: Exception) {
                mlog("读取脚本内容失败: ${e.message}")
                null
            }
        } else {
            null
        }
    }
}